{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "PROJECT_ID = 'qwiklabs-gcp-da02053fb2a13c97' # CHANGE THIS\n",
    "BUCKET = 'qwiklabs-gcp-da02053fb2a13c97' # CHANGE THIS\n",
    "MODEL_BASE = 'taxi_trained/export/exporter'\n",
    "MODEL_PATH = os.path.join(MODEL_BASE,os.listdir(MODEL_BASE)[-1])\n",
    "MODEL_NAME = 'taxifare'\n",
    "VERSION_NAME = 'v1'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deploy for Online Prediction\n",
    "\n",
    "To get our predictions, in addition to the features provided by the client, we also need to fetch the latest traffic information from BigQuery. We then combine these and invoke our tensorflow model. This is visualized by the 'on-demand' portion (red arrows) in the below diagram:\n",
    "\n",
    "<img src=\"../../taxicab_traffic/assets/architecture.png\" >\n",
    "\n",
    "To do this we'll take advantage of [AI Platforms Custom Prediction Routines](https://cloud.google.com/ml-engine/docs/tensorflow/custom-prediction-routines) which allows us to execute custom python code in response to every online prediction request. There are 5 steps to creating a custom prediction routine:\n",
    "\n",
    "1. Upload Model Artifacts to GCS\n",
    "2. Implement Predictor interface \n",
    "3. Package the prediction code and dependencies\n",
    "4. Deploy\n",
    "5. Invoke API"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Upload Model Artifacts to GCS\n",
    "\n",
    "Here we upload our model weights so that AI Platform can access them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!gcloud storage cp --recursive $MODEL_PATH/* gs://$BUCKET/taxifare/model/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Implement Predictor Interface\n",
    "\n",
    "Interface Spec: https://cloud.google.com/ml-engine/docs/tensorflow/custom-prediction-routines#predictor-class\n",
    "\n",
    "This tells AI Platform how to load the model artifacts, and is where we specify our custom prediction code.\n",
    "\n",
    "**Excercise 1:** Complete the SQL `query_string` to return the latest (proxy) traffic information. To check your answer reference the solution.\n",
    "\n",
    "Note: the correct PROJECT_ID will automatically be inserted using the bash `sed` command in the subsequent cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile predictor.py\n",
    "import tensorflow as tf\n",
    "from google.cloud import bigquery\n",
    "\n",
    "PROJECT_ID = 'will_be_replaced'\n",
    "\n",
    "class TaxifarePredictor(object):\n",
    "    def __init__(self, predict_fn):\n",
    "      self.predict_fn = predict_fn   \n",
    "    \n",
    "    def predict(self, instances, **kwargs):\n",
    "        bq = bigquery.Client(PROJECT_ID)\n",
    "        query_string = \"\"\"\n",
    "        ###TODO###\n",
    "        \"\"\"\n",
    "        trips = bq.query(query_string).to_dataframe()['trips_last_5min'][0]\n",
    "        instances['trips_last_5min'] = [trips for _ in range(len(list(instances.items())[0][1]))]\n",
    "        predictions = self.predict_fn(instances)\n",
    "        return predictions['predictions'].tolist() # convert to list so it is JSON serialiable (requirement)\n",
    "    \n",
    "\n",
    "    @classmethod\n",
    "    def from_path(cls, model_dir):\n",
    "        predict_fn = tf.contrib.predictor.from_saved_model(model_dir,'predict')\n",
    "        return cls(predict_fn)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!sed -i -e 's/will_be_replaced/{PROJECT_ID}/g' predictor.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Test Predictor Class Works Locally"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import predictor\n",
    "\n",
    "instances = {'dayofweek' : [6,5],\n",
    "             'hourofday' : [12,11],\n",
    "             'pickuplon' : [-73.99,-73.99], \n",
    "             'pickuplat' : [40.758,40.758],\n",
    "             'dropofflat' : [40.742,40.758],\n",
    "             'dropofflon' : [-73.97,-73.97]}\n",
    "\n",
    "predictor = predictor.TaxifarePredictor.from_path(MODEL_PATH)\n",
    "predictor.predict(instances)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Package Predictor Class and Dependencies\n",
    "\n",
    "We must package the predictor as a tar.gz source distribution package. Instructions for this are specified [here](http://cloud.google.com/ml-engine/docs/custom-prediction-routines#predictor-tarball). The AI Platform runtime comes preinstalled with several packages [listed here](https://cloud.google.com/ml-engine/docs/tensorflow/runtime-version-list). However it does not come with `google-cloud-bigquery` so we list that as a dependency below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile setup.py\n",
    "from setuptools import setup\n",
    "\n",
    "setup(\n",
    "    name='taxifare_custom_predict_code',\n",
    "    version='0.1',\n",
    "    scripts=['predictor.py'],\n",
    "    install_requires=[\n",
    "        'google-cloud-bigquery==1.16.0',\n",
    "    ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!python setup.py sdist --formats=gztar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!gcloud storage cp dist/taxifare_custom_predict_code-0.1.tar.gz gs://$BUCKET/taxifare/predict_code/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Deploy\n",
    "\n",
    "This is similar to how we deploy standard models to AI Platform, with a few extra command line arguments.\n",
    "\n",
    "Note the use of the `--service-acount` parameter below.\n",
    "\n",
    "The default service account does not have permissions to read from BigQuery, so we [specify a different service account](https://cloud.google.com/ml-engine/docs/tensorflow/deploying-models#service-account) that does have permission.\n",
    "\n",
    "Specifically we use the [Compute Engine default service account](https://cloud.google.com/compute/docs/access/service-accounts#compute_engine_default_service_account) which has the IAM project editor role."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!gcloud beta ai-platform models create $MODEL_NAME --regions us-central1 --enable-logging --enable-console-logging\n",
    "#!gcloud ai-platform versions delete $VERSION_NAME --model taxifare --quiet\n",
    "!gcloud beta ai-platform versions create $VERSION_NAME \\\n",
    "  --model $MODEL_NAME \\\n",
    "  --origin gs://$BUCKET/taxifare/model \\\n",
    "  --service-account $(gcloud projects list --filter=\"$PROJECT_ID\" --format=\"value(PROJECT_NUMBER)\")-compute@developer.gserviceaccount.com \\\n",
    "  --runtime-version 1.14 \\\n",
    "  --python-version 3.5 \\\n",
    "  --package-uris gs://$BUCKET/taxifare/predict_code/taxifare_custom_predict_code-0.1.tar.gz \\\n",
    "  --prediction-class predictor.TaxifarePredictor"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Invoke API"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Warning:** You will see `ImportError: file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth` when you run this. While it looks like an error this is actually just a warning and is safe to ignore, the subsequent cell will still work."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import googleapiclient.discovery\n",
    "\n",
    "instances = {'dayofweek' : [6], \n",
    "             'hourofday' : [12],\n",
    "             'pickuplon' : [-73.99], \n",
    "             'pickuplat' : [40.758],\n",
    "             'dropofflat' : [40.742],\n",
    "             'dropofflon' : [-73.97]}\n",
    "\n",
    "service = googleapiclient.discovery.build('ml', 'v1')\n",
    "name = 'projects/{}/models/{}/versions/{}'.format(PROJECT_ID, MODEL_NAME, VERSION_NAME)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "response = service.projects().predict(\n",
    "    name=name,\n",
    "    body={'instances': instances}\n",
    ").execute()\n",
    "\n",
    "if 'error' in response:\n",
    "    raise RuntimeError(response['error'])\n",
    "else:\n",
    "  print(response['predictions'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try re-running the query again after 15 seconds (the windowing period for DataFlow), note how the prediction changes in response to the new traffic data!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
